<?php

/**
 * JPGraph v4.0.3
 */

namespace lemon\JpGraph\Graph;

use lemon\JpGraph\Text;
use lemon\JpGraph\Util;

/**
 * @class GanttScale
 * // Description: Responsible for calculating and showing
 * // the scale in a gantt chart. This includes providing methods for
 * // converting dates to position in the chart as well as stroking the
 * // date headers (days, week, etc).
 */
class GanttScale
{
	public $minute;
	public $hour;
	public $day;
	public $week;
	public $month;
	public $year;
	public $divider;
	public $dividerh;
	public $tableTitle;
	public $iStartDate = -1;
	public $iEndDate   = -1;
	// Number of gantt bar position (n.b not necessariliy the same as the number of bars)
	// we could have on bar in position 1, and one bar in position 5 then there are two
	// bars but the number of bar positions is 5
	public $actinfo;
	public $iTopPlotMargin	= 10;
	public $iBottomPlotMargin = 15;
	public $iVertLines		= -1;
	public $iVertHeaderSize   = -1;
	// The width of the labels (defaults to the widest of all labels)
	private $iLabelWidth;
	// Out image to stroke the scale to
	private $iImg;
	private $iTableHeaderBackgroundColor = 'white';
	private $iTableHeaderFrameColor	  = 'black';
	private $iTableHeaderFrameWeight	 = 1;
	private $iAvailableHeight			= -1;
	private $iVertSpacing				= -1;
	private $iDateLocale;
	private $iVertLayout			   = GANTT_EVEN;
	private $iUsePlotWeekendBackground = true;
	private $iWeekStart				= 1; // Default to have weekends start on Monday

	/**
	 * CONSTRUCTOR.
	 *
	 * @param mixed $aImg
	 */
	public function __construct($aImg)
	{
		$this->iImg		= $aImg;
		$this->iDateLocale = new Util\DateLocale();

		$this->minute = new HeaderProperty();
		$this->minute->SetIntervall(15);
		$this->minute->SetLabelFormatString('i');
		$this->minute->SetFont(FF_FONT0);
		$this->minute->grid->SetColor('gray');

		$this->hour = new HeaderProperty();
		$this->hour->SetFont(FF_FONT0);
		$this->hour->SetIntervall(6);
		$this->hour->SetStyle(HOURSTYLE_HM24);
		$this->hour->SetLabelFormatString('H:i');
		$this->hour->grid->SetColor('gray');

		$this->day = new HeaderProperty();
		$this->day->grid->SetColor('gray');
		$this->day->SetLabelFormatString('l');

		$this->week = new HeaderProperty();
		$this->week->SetLabelFormatString('w%d');
		$this->week->SetFont(FF_FONT1);

		$this->month = new HeaderProperty();
		$this->month->SetFont(FF_FONT1, FS_BOLD);

		$this->year = new HeaderProperty();
		$this->year->SetFont(FF_FONT1, FS_BOLD);

		$this->divider  = new LineProperty();
		$this->dividerh = new LineProperty();
		$this->dividerh->SetWeight(2);
		$this->divider->SetWeight(6);
		$this->divider->SetColor('gray');
		$this->divider->SetStyle('fancy');

		$this->tableTitle = new Text\TextProperty();
		$this->tableTitle->Show(false);
		$this->actinfo = new GanttActivityInfo();
	}

	/**
	 * PUBLIC METHODS
	 * // Specify what headers should be visible.
	 *
	 * @param mixed $aFlg
	 */
	public function ShowHeaders($aFlg)
	{
		$this->day->Show($aFlg & GANTT_HDAY);
		$this->week->Show($aFlg & GANTT_HWEEK);
		$this->month->Show($aFlg & GANTT_HMONTH);
		$this->year->Show($aFlg & GANTT_HYEAR);
		$this->hour->Show($aFlg & GANTT_HHOUR);
		$this->minute->Show($aFlg & GANTT_HMIN);

		// Make some default settings of gridlines whihc makes sense
		if ($aFlg & GANTT_HWEEK) {
			$this->month->grid->Show(false);
			$this->year->grid->Show(false);
		}
		if ($aFlg & GANTT_HHOUR) {
			$this->day->grid->SetColor('black');
		}
	}

	// Should the weekend background stretch all the way down in the plotarea
	public function UseWeekendBackground($aShow)
	{
		$this->iUsePlotWeekendBackground = $aShow;
	}

	// Have a range been specified?
	public function IsRangeSet()
	{
		return $this->iStartDate != -1 && $this->iEndDate != -1;
	}

	// Should the layout be from top or even?
	public function SetVertLayout($aLayout)
	{
		$this->iVertLayout = $aLayout;
	}

	// Which locale should be used?
	public function SetDateLocale($aLocale)
	{
		$this->iDateLocale->Set($aLocale);
	}

	// Number of days we are showing
	public function GetNumberOfDays()
	{
		return round(($this->iEndDate - $this->iStartDate) / SECPERDAY);
	}

	// The width of the actual plot area
	public function GetPlotWidth()
	{
		$img = $this->iImg;

		return $img->width - $img->left_margin - $img->right_margin;
	}

	// Specify the width of the titles(labels) for the activities
	// (This is by default set to the minimum width enought for the
	// widest title)
	public function SetLabelWidth($aLabelWidth)
	{
		$this->iLabelWidth = $aLabelWidth;
	}

	// Which day should the week start?
	// 0==Sun, 1==Monday, 2==Tuesday etc
	public function SetWeekStart($aStartDay)
	{
		$this->iWeekStart = $aStartDay % 7;

		//Recalculate the startday since this will change the week start
		$this->SetRange($this->iStartDate, $this->iEndDate);
	}

	// Do we show min scale?
	public function IsDisplayMinute()
	{
		return $this->minute->iShowLabels;
	}

	// Do we show day scale?
	public function IsDisplayHour()
	{
		return $this->hour->iShowLabels;
	}

	// Do we show day scale?
	public function IsDisplayDay()
	{
		return $this->day->iShowLabels;
	}

	// Do we show week scale?
	public function IsDisplayWeek()
	{
		return $this->week->iShowLabels;
	}

	// Do we show month scale?
	public function IsDisplayMonth()
	{
		return $this->month->iShowLabels;
	}

	// Do we show year scale?
	public function IsDisplayYear()
	{
		return $this->year->iShowLabels;
	}

	// Specify spacing (in percent of bar height) between activity bars
	public function SetVertSpacing($aSpacing)
	{
		$this->iVertSpacing = $aSpacing;
	}

	// Specify scale min and max date either as timestamp or as date strings
	// Always round to the nearest week boundary
	public function SetRange($aMin, $aMax)
	{
		$this->iStartDate = $this->NormalizeDate($aMin);
		$this->iEndDate   = $this->NormalizeDate($aMax);
	}

	// Adjust the start and end date so they fit to beginning/ending
	// of the week taking the specified week start day into account.
	public function AdjustStartEndDay()
	{
		if (!($this->IsDisplayYear() || $this->IsDisplayMonth() || $this->IsDisplayWeek())) {
			// Don't adjust
			return;
		}

		// Get day in week for start and ending date (Sun==0)
		$ds = strftime('%w', $this->iStartDate);
		$de = strftime('%w', $this->iEndDate);

		// We want to start on iWeekStart day. But first we subtract a week
		// if the startdate is "behind" the day the week start at.
		// This way we ensure that the given start date is always included
		// in the range. If we don't do this the nearest correct weekday in the week
		// to start at might be later than the start date.
		if ($ds < $this->iWeekStart) {
			$d = strtotime('-7 day', $this->iStartDate);
		} else {
			$d = $this->iStartDate;
		}

		$adjdate		  = strtotime(($this->iWeekStart - $ds) . ' day', $d/*$this->iStartDate*/);
		$this->iStartDate = $adjdate;

		// We want to end on the last day of the week
		$preferredEndDay = ($this->iWeekStart + 6) % 7;
		if ($preferredEndDay != $de) {
			// Solve equivalence eq:	$de + x ~ $preferredDay (mod 7)
			$adj			= (7 + ($preferredEndDay - $de)) % 7;
			$adjdate		= strtotime("+${adj} day", $this->iEndDate);
			$this->iEndDate = $adjdate;
		}
	}

	// Specify background for the table title area (upper left corner of the table)
	public function SetTableTitleBackground($aColor)
	{
		$this->iTableHeaderBackgroundColor = $aColor;
	}

	///////////////////////////////////////
	// PRIVATE Methods

	// Determine the height of all the scale headers combined
	public function GetHeaderHeight()
	{
		$img	= $this->iImg;
		$height = 1;
		if ($this->minute->iShowLabels) {
			$height += $this->minute->GetFontHeight($img);
			$height += $this->minute->iTitleVertMargin;
		}
		if ($this->hour->iShowLabels) {
			$height += $this->hour->GetFontHeight($img);
			$height += $this->hour->iTitleVertMargin;
		}
		if ($this->day->iShowLabels) {
			$height += $this->day->GetFontHeight($img);
			$height += $this->day->iTitleVertMargin;
		}
		if ($this->week->iShowLabels) {
			$height += $this->week->GetFontHeight($img);
			$height += $this->week->iTitleVertMargin;
		}
		if ($this->month->iShowLabels) {
			$height += $this->month->GetFontHeight($img);
			$height += $this->month->iTitleVertMargin;
		}
		if ($this->year->iShowLabels) {
			$height += $this->year->GetFontHeight($img);
			$height += $this->year->iTitleVertMargin;
		}

		return $height;
	}

	// Get width (in pixels) for a single day
	public function GetDayWidth()
	{
		return ($this->GetPlotWidth() - $this->iLabelWidth + 1) / $this->GetNumberOfDays();
	}

	// Get width (in pixels) for a single hour
	public function GetHourWidth()
	{
		return $this->GetDayWidth() / 24;
	}

	public function GetMinuteWidth()
	{
		return $this->GetHourWidth() / 60;
	}

	// Nuber of days in a year
	public function GetNumDaysInYear($aYear)
	{
		if ($this->IsLeap($aYear)) {
			return 366;
		}

		return 365;
	}

	// Get week number
	public function GetWeekNbr($aDate, $aSunStart = true)
	{
		// We can't use the internal strftime() since it gets the weeknumber
		// wrong since it doesn't follow ISO on all systems since this is
		// system linrary dependent.
		// Even worse is that this works differently if we are on a Windows
		// or UNIX box (it even differs between UNIX boxes how strftime()
		// is natively implemented)
		//
		// Credit to Nicolas Hoizey <nhoizey@phpheaven.net> for this elegant
		// version of Week Nbr calculation.

		$day = $this->NormalizeDate($aDate);
		if ($aSunStart) {
			$day += 60 * 60 * 24;
		}

		/**
		 * According to ISO-8601 :
		 * "Week 01 of a year is per definition the first week that has the Thursday in this year,
		 * which is equivalent to the week that contains the fourth day of January.
		 * In other words, the first week of a new year is the week that has the majority of its
		 * days in the new year.".
		 *
		 * Be carefull, with PHP, -3 % 7 = -3, instead of 4 !!!
		 *
		 * day of year			 = date("z", $day) + 1
		 * offset to thursday	  = 3 - (date("w", $day) + 6) % 7
		 * first thursday of year  = 1 + (11 - date("w", mktime(0, 0, 0, 1, 1, date("Y", $day)))) % 7
		 * week number			 = (thursday's day of year - first thursday's day of year) / 7 + 1
		 * ---------------------------------------------------------------------------*/
		$thursday = $day + 60 * 60 * 24 * (3 - (date('w', $day) + 6) % 7); // take week's thursday
		$week	 = 1 + (date('z', $thursday) - (11 - date('w', mktime(0, 0, 0, 1, 1, date('Y', $thursday)))) % 7) / 7;

		return $week;
	}

	// Is year a leap year?
	public function IsLeap($aYear)
	{
		// Is the year a leap year?
		//$year = 0+date("Y",$aDate);
		if ($aYear % 4 == 0) {
			if (!($aYear % 100 == 0) || ($aYear % 400 == 0)) {
				return true;
			}
		}

		return false;
	}

	// Get current year
	public function GetYear($aDate)
	{
		return 0 + date('Y', $aDate);
	}

	// Return number of days in a year
	public function GetNumDaysInMonth($aMonth, $aYear)
	{
		$days  = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
		$daysl = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
		if ($this->IsLeap($aYear)) {
			return $daysl[$aMonth];
		}

		return $days[$aMonth];
	}

	// Get day in month
	public function GetMonthDayNbr($aDate)
	{
		return 0 + strftime('%d', $aDate);
	}

	// Get day in year
	public function GetYearDayNbr($aDate)
	{
		return 0 + strftime('%j', $aDate);
	}

	// Get month number
	public function GetMonthNbr($aDate)
	{
		return 0 + strftime('%m', $aDate);
	}

	// Translate a date to screen coordinates (horizontal scale)
	public function TranslateDate($aDate)
	{
		//
		// In order to handle the problem with Daylight savings time
		// the scale written with equal number of seconds per day beginning
		// with the start date. This means that we "cement" the state of
		// DST as it is in the start date. If later the scale includes the
		// switchover date (depends on the locale) we need to adjust back
		// if the date we try to translate has a different DST status since
		// we would otherwise be off by one hour.
		$aDate  = $this->NormalizeDate($aDate);
		$tmp	= localtime($aDate);
		$cloc   = $tmp[8];
		$tmp	= localtime($this->iStartDate);
		$sloc   = $tmp[8];
		$offset = 0;
		if ($sloc != $cloc) {
			if ($sloc) {
				$offset = 3600;
			} else {
				$offset = -3600;
			}
		}
		$img = $this->iImg;

		return ($aDate - $this->iStartDate - $offset) / SECPERDAY * $this->GetDayWidth() + $img->left_margin + $this->iLabelWidth;
	}

	// Get screen coordinatesz for the vertical position for a bar
	public function TranslateVertPos($aPos, $atTop = false)
	{
		$img = $this->iImg;
		if ($aPos > $this->iVertLines) {
			Util\JpGraphError::RaiseL(6015, $aPos);
		}

		// 'Illegal vertical position %d'
		if ($this->iVertLayout == GANTT_EVEN) {
			// Position the top bar at 1 vert spacing from the scale
			$pos = round($img->top_margin + $this->iVertHeaderSize + ($aPos + 1) * $this->iVertSpacing);
		} else {
			// position the top bar at 1/2 a vert spacing from the scale
			$pos = round($img->top_margin + $this->iVertHeaderSize + $this->iTopPlotMargin + ($aPos + 1) * $this->iVertSpacing);
		}

		if ($atTop) {
			$pos -= $this->iVertSpacing;
		}

		return $pos;
	}

	// What is the vertical spacing?
	public function GetVertSpacing()
	{
		return $this->iVertSpacing;
	}

	// Convert a date to timestamp
	public function NormalizeDate($aDate)
	{
		if ($aDate === false) {
			return false;
		}

		if (is_string($aDate)) {
			$t = strtotime($aDate);
			if ($t === false || $t === -1) {
				Util\JpGraphError::RaiseL(6016, $aDate);
				//("Date string ($aDate) specified for Gantt activity can not be interpretated. Please make sure it is a valid time string, e.g. 2005-04-23 13:30");
			}

			return $t;
		}
		if (is_int($aDate) || is_float($aDate)) {
			return $aDate;
		}
		Util\JpGraphError::RaiseL(6017, $aDate);

		//Unknown date format in GanttScale ($aDate).");
	}

	// Convert a time string to minutes

	public function TimeToMinutes($aTimeString)
	{
		// Split in hours and minutes
		$pos	= strpos($aTimeString, ':');
		$minint = 60;
		if ($pos === false) {
			$hourint = $aTimeString;
			$minint  = 0;
		} else {
			$hourint = floor(substr($aTimeString, 0, $pos));
			$minint  = floor(substr($aTimeString, $pos + 1));
		}
		$minint += 60 * $hourint;

		return $minint;
	}

	// Stroke the day scale (including gridlines)
	public function StrokeMinutes($aYCoord, $getHeight = false)
	{
		$img = $this->iImg;
		$xt  = $img->left_margin + $this->iLabelWidth;
		$yt  = $aYCoord + $img->top_margin;
		if ($this->minute->iShowLabels) {
			$img->SetFont($this->minute->iFFamily, $this->minute->iFStyle, $this->minute->iFSize);
			$yb = $yt + $img->GetFontHeight() +
			$this->minute->iTitleVertMargin + $this->minute->iFrameWeight;
			if ($getHeight) {
				return $yb - $img->top_margin;
			}
			$xb = $img->width - $img->right_margin + 1;
			$img->SetColor($this->minute->iBackgroundColor);
			$img->FilledRectangle($xt, $yt, $xb, $yb);

			$x = $xt;
			$img->SetTextAlign('center');
			$day	= date('w', $this->iStartDate);
			$minint = $this->minute->GetIntervall();

			if (60 % $minint !== 0) {
				Util\JpGraphError::RaiseL(6018, $minint);
				//'Intervall for minutes must divide the hour evenly, e.g. 1,5,10,12,15,20,30 etc You have specified an intervall of '.$minint.' minutes.');
			}

			$n		 = 60 / $minint;
			$datestamp = $this->iStartDate;
			$width	 = $this->GetHourWidth() / $n;
			if ($width < 8) {
				// TO small width to draw minute scale
				Util\JpGraphError::RaiseL(6019, $width);
				//('The available width ('.$width.') for minutes are to small for this scale to be displayed. Please use auto-sizing or increase the width of the graph.');
			}

			$nh = ceil(24 * 60 / $this->TimeToMinutes($this->hour->GetIntervall()));
			$nd = $this->GetNumberOfDays();
			// Convert to intervall to seconds
			$minint *= 60;
			for ($j = 0; $j < $nd; ++$j, ++$day, $day %= 7) {
				for ($k = 0; $k < $nh; ++$k) {
					for ($i = 0; $i < $n; ++$i, $x += $width, $datestamp += $minint) {
						if ($day == 6 || $day == 0) {
							$img->PushColor($this->day->iWeekendBackgroundColor);
							if ($this->iUsePlotWeekendBackground) {
								$img->FilledRectangle($x, $yt + $this->day->iFrameWeight, $x + $width, $img->height - $img->bottom_margin);
							} else {
								$img->FilledRectangle($x, $yt + $this->day->iFrameWeight, $x + $width, $yb - $this->day->iFrameWeight);
							}

							$img->PopColor();
						}

						if ($day == 0) {
							$img->SetColor($this->day->iSundayTextColor);
						} else {
							$img->SetColor($this->day->iTextColor);
						}

						switch ($this->minute->iStyle) {
							case MINUTESTYLE_CUSTOM:
								$txt = date($this->minute->iLabelFormStr, $datestamp);

								break;
							case MINUTESTYLE_MM:
							default:
								// 15
								$txt = date('i', $datestamp);

								break;
						}
						$img->StrokeText(round($x + $width / 2), round($yb - $this->minute->iTitleVertMargin), $txt);

						// Fix a rounding problem the wrong way ..
						// If we also have hour scale then don't draw the firsta or last
						// gridline since that will be overwritten by the hour scale gridline if such exists.
						// However, due to the propagation of rounding of the 'x+=width' term in the loop
						// this might sometimes be one pixel of so we fix this by not drawing it.
						// The proper way to fix it would be to re-calculate the scale for each step and
						// not using the additive term.
						if (!(($i == $n || $i == 0) && $this->hour->iShowLabels && $this->hour->grid->iShow)) {
							$img->SetColor($this->minute->grid->iColor);
							$img->SetLineWeight($this->minute->grid->iWeight);
							$img->Line($x, $yt, $x, $yb);
							$this->minute->grid->Stroke($img, $x, $yb, $x, $img->height - $img->bottom_margin);
						}
					}
				}
			}
			$img->SetColor($this->minute->iFrameColor);
			$img->SetLineWeight($this->minute->iFrameWeight);
			$img->Rectangle($xt, $yt, $xb, $yb);

			return $yb - $img->top_margin;
		}

		return $aYCoord;
	}

	// Stroke the day scale (including gridlines)
	public function StrokeHours($aYCoord, $getHeight = false)
	{
		$img = $this->iImg;
		$xt  = $img->left_margin + $this->iLabelWidth;
		$yt  = $aYCoord + $img->top_margin;
		if ($this->hour->iShowLabels) {
			$img->SetFont($this->hour->iFFamily, $this->hour->iFStyle, $this->hour->iFSize);
			$yb = $yt + $img->GetFontHeight() +
			$this->hour->iTitleVertMargin + $this->hour->iFrameWeight;
			if ($getHeight) {
				return $yb - $img->top_margin;
			}
			$xb = $img->width - $img->right_margin + 1;
			$img->SetColor($this->hour->iBackgroundColor);
			$img->FilledRectangle($xt, $yt, $xb, $yb);

			$x = $xt;
			$img->SetTextAlign('center');
			$tmp	= $this->hour->GetIntervall();
			$minint = $this->TimeToMinutes($tmp);
			if (1440 % $minint !== 0) {
				Util\JpGraphError::RaiseL(6020, $tmp);
				//('Intervall for hours must divide the day evenly, e.g. 0:30, 1:00, 1:30, 4:00 etc. You have specified an intervall of '.$tmp);
			}

			$n		 = ceil(24 * 60 / $minint);
			$datestamp = $this->iStartDate;
			$day	   = date('w', $this->iStartDate);
			$doback	= !$this->minute->iShowLabels;
			$width	 = $this->GetDayWidth() / $n;
			for ($j = 0; $j < $this->GetNumberOfDays(); ++$j, ++$day, $day %= 7) {
				for ($i = 0; $i < $n; ++$i, $x += $width) {
					if ($day == 6 || $day == 0) {
						$img->PushColor($this->day->iWeekendBackgroundColor);
						if ($this->iUsePlotWeekendBackground && $doback) {
							$img->FilledRectangle($x, $yt + $this->day->iFrameWeight, $x + $width, $img->height - $img->bottom_margin);
						} else {
							$img->FilledRectangle($x, $yt + $this->day->iFrameWeight, $x + $width, $yb - $this->day->iFrameWeight);
						}

						$img->PopColor();
					}

					if ($day == 0) {
						$img->SetColor($this->day->iSundayTextColor);
					} else {
						$img->SetColor($this->day->iTextColor);
					}

					switch ($this->hour->iStyle) {
						case HOURSTYLE_HMAMPM:
							// 1:35pm
							$txt = date('g:ia', $datestamp);

							break;
						case HOURSTYLE_H24:
							// 13
							$txt = date('H', $datestamp);

							break;
						case HOURSTYLE_HAMPM:
							$txt = date('ga', $datestamp);

							break;
						case HOURSTYLE_CUSTOM:
							$txt = date($this->hour->iLabelFormStr, $datestamp);

							break;
						case HOURSTYLE_HM24:
						default:
							$txt = date('H:i', $datestamp);

							break;
					}
					$img->StrokeText(round($x + $width / 2), round($yb - $this->hour->iTitleVertMargin), $txt);
					$img->SetColor($this->hour->grid->iColor);
					$img->SetLineWeight($this->hour->grid->iWeight);
					$img->Line($x, $yt, $x, $yb);
					$this->hour->grid->Stroke($img, $x, $yb, $x, $img->height - $img->bottom_margin);
					//$datestamp += $minint*60
					$datestamp = mktime(
						date('H', $datestamp),
						date('i', $datestamp) + $minint,
						0,
						date('m', $datestamp),
						date('d', $datestamp) + 1,
						date('Y', $datestamp)
					);
				}
			}
			$img->SetColor($this->hour->iFrameColor);
			$img->SetLineWeight($this->hour->iFrameWeight);
			$img->Rectangle($xt, $yt, $xb, $yb);

			return $yb - $img->top_margin;
		}

		return $aYCoord;
	}

	// Stroke the day scale (including gridlines)
	public function StrokeDays($aYCoord, $getHeight = false)
	{
		$img	  = $this->iImg;
		$daywidth = $this->GetDayWidth();
		$xt	   = $img->left_margin + $this->iLabelWidth;
		$yt	   = $aYCoord + $img->top_margin;
		if ($this->day->iShowLabels) {
			$img->SetFont($this->day->iFFamily, $this->day->iFStyle, $this->day->iFSize);
			$yb = $yt + $img->GetFontHeight() + $this->day->iTitleVertMargin + $this->day->iFrameWeight;
			if ($getHeight) {
				return $yb - $img->top_margin;
			}
			$xb = $img->width - $img->right_margin + 1;
			$img->SetColor($this->day->iBackgroundColor);
			$img->FilledRectangle($xt, $yt, $xb, $yb);

			$x = $xt;
			$img->SetTextAlign('center');
			$day	   = date('w', $this->iStartDate);
			$datestamp = $this->iStartDate;

			$doback = !($this->hour->iShowLabels || $this->minute->iShowLabels);

			setlocale(LC_TIME, $this->iDateLocale->iLocale);

			for ($i = 0; $i < $this->GetNumberOfDays(); ++$i, $x += $daywidth, ++$day, $day %= 7) {
				if ($day == 6 || $day == 0) {
					$img->SetColor($this->day->iWeekendBackgroundColor);
					if ($this->iUsePlotWeekendBackground && $doback) {
						$img->FilledRectangle(
							$x,
							$yt + $this->day->iFrameWeight,
							$x + $daywidth,
							$img->height - $img->bottom_margin
						);
					} else {
						$img->FilledRectangle(
							$x,
							$yt + $this->day->iFrameWeight,
							$x + $daywidth,
							$yb - $this->day->iFrameWeight
						);
					}
				}

				$mn = strftime('%m', $datestamp);
				if ($mn[0] == '0') {
					$mn = $mn[1];
				}

				switch ($this->day->iStyle) {
					case DAYSTYLE_LONG:
						// "Monday"
						$txt = strftime('%A', $datestamp);

						break;
					case DAYSTYLE_SHORT:
						// "Mon"
						$txt = strftime('%a', $datestamp);

						break;
					case DAYSTYLE_SHORTDAYDATE1:
						// "Mon 23/6"
						$txt = strftime('%a %d/' . $mn, $datestamp);

						break;
					case DAYSTYLE_SHORTDAYDATE2:
						// "Mon 23 Jun"
						$txt = strftime('%a %d %b', $datestamp);

						break;
					case DAYSTYLE_SHORTDAYDATE3:
						// "Mon 23 Jun 2003"
						$txt = strftime('%a %d %b %Y', $datestamp);

						break;
					case DAYSTYLE_LONGDAYDATE1:
						// "Monday 23 Jun"
						$txt = strftime('%A %d %b', $datestamp);

						break;
					case DAYSTYLE_LONGDAYDATE2:
						// "Monday 23 Jun 2003"
						$txt = strftime('%A %d %b %Y', $datestamp);

						break;
					case DAYSTYLE_SHORTDATE1:
						// "23/6"
						$txt = strftime('%d/' . $mn, $datestamp);

						break;
					case DAYSTYLE_SHORTDATE2:
						// "23 Jun"
						$txt = strftime('%d %b', $datestamp);

						break;
					case DAYSTYLE_SHORTDATE3:
						// "Mon 23"
						$txt = strftime('%a %d', $datestamp);

						break;
					case DAYSTYLE_SHORTDATE4:
						// "23"
						$txt = strftime('%d', $datestamp);

						break;
					case DAYSTYLE_CUSTOM:
						// Custom format
						$txt = strftime($this->day->iLabelFormStr, $datestamp);

						break;
					case DAYSTYLE_ONELETTER:
					default:
						// "M"
						$txt = strftime('%A', $datestamp);
						$txt = strtoupper($txt[0]);

						break;
				}

				if ($day == 0) {
					$img->SetColor($this->day->iSundayTextColor);
				} else {
					$img->SetColor($this->day->iTextColor);
				}

				$img->StrokeText(
					round($x + $daywidth / 2 + 1),
					round($yb - $this->day->iTitleVertMargin),
					$txt
				);
				$img->SetColor($this->day->grid->iColor);
				$img->SetLineWeight($this->day->grid->iWeight);
				$img->Line($x, $yt, $x, $yb);
				$this->day->grid->Stroke($img, $x, $yb, $x, $img->height - $img->bottom_margin);
				$datestamp = mktime(0, 0, 0, date('m', $datestamp), date('d', $datestamp) + 1, date('Y', $datestamp));
				//$datestamp += SECPERDAY;
			}
			$img->SetColor($this->day->iFrameColor);
			$img->SetLineWeight($this->day->iFrameWeight);
			$img->Rectangle($xt, $yt, $xb, $yb);

			return $yb - $img->top_margin;
		}

		return $aYCoord;
	}

	// Stroke week header and grid
	public function StrokeWeeks($aYCoord, $getHeight = false)
	{
		if ($this->week->iShowLabels) {
			$img = $this->iImg;
			$yt  = $aYCoord + $img->top_margin;
			$img->SetFont($this->week->iFFamily, $this->week->iFStyle, $this->week->iFSize);
			$yb = $yt + $img->GetFontHeight() + $this->week->iTitleVertMargin + $this->week->iFrameWeight;

			if ($getHeight) {
				return $yb - $img->top_margin;
			}

			$xt		= $img->left_margin + $this->iLabelWidth;
			$weekwidth = $this->GetDayWidth() * 7;
			$wdays	 = $this->iDateLocale->GetDayAbb();
			$xb		= $img->width - $img->right_margin + 1;
			$week	  = $this->iStartDate;
			$weeknbr   = $this->GetWeekNbr($week);
			$img->SetColor($this->week->iBackgroundColor);
			$img->FilledRectangle($xt, $yt, $xb, $yb);
			$img->SetColor($this->week->grid->iColor);
			$x = $xt;
			if ($this->week->iStyle == WEEKSTYLE_WNBR) {
				$img->SetTextAlign('center');
				$txtOffset = $weekwidth / 2 + 1;
			} elseif ($this->week->iStyle == WEEKSTYLE_FIRSTDAY ||
				$this->week->iStyle == WEEKSTYLE_FIRSTDAY2 ||
				$this->week->iStyle == WEEKSTYLE_FIRSTDAYWNBR ||
				$this->week->iStyle == WEEKSTYLE_FIRSTDAY2WNBR) {
				$img->SetTextAlign('left');
				$txtOffset = 3;
			} else {
				Util\JpGraphError::RaiseL(6021);
				//("Unknown formatting style for week.");
			}

			for ($i = 0; $i < $this->GetNumberOfDays() / 7; ++$i, $x += $weekwidth) {
				$img->PushColor($this->week->iTextColor);

				if ($this->week->iStyle == WEEKSTYLE_WNBR) {
					$txt = sprintf($this->week->iLabelFormStr, $weeknbr);
				} elseif ($this->week->iStyle == WEEKSTYLE_FIRSTDAY ||
					$this->week->iStyle == WEEKSTYLE_FIRSTDAYWNBR) {
					$txt = date('j/n', $week);
				} elseif ($this->week->iStyle == WEEKSTYLE_FIRSTDAY2 ||
					$this->week->iStyle == WEEKSTYLE_FIRSTDAY2WNBR) {
					$monthnbr   = date('n', $week) - 1;
					$shortmonth = $this->iDateLocale->GetShortMonthName($monthnbr);
					$txt		= date('j', $week) . ' ' . $shortmonth;
				}

				if ($this->week->iStyle == WEEKSTYLE_FIRSTDAYWNBR ||
					$this->week->iStyle == WEEKSTYLE_FIRSTDAY2WNBR) {
					$w = sprintf($this->week->iLabelFormStr, $weeknbr);
					$txt .= ' ' . $w;
				}

				$img->StrokeText(
					round($x + $txtOffset),
					round($yb - $this->week->iTitleVertMargin),
					$txt
				);

				$week	= strtotime('+7 day', $week);
				$weeknbr = $this->GetWeekNbr($week);
				$img->PopColor();
				$img->SetLineWeight($this->week->grid->iWeight);
				$img->Line($x, $yt, $x, $yb);
				$this->week->grid->Stroke($img, $x, $yb, $x, $img->height - $img->bottom_margin);
			}
			$img->SetColor($this->week->iFrameColor);
			$img->SetLineWeight($this->week->iFrameWeight);
			$img->Rectangle($xt, $yt, $xb, $yb);

			return $yb - $img->top_margin;
		}

		return $aYCoord;
	}

	// Format the mont scale header string
	public function GetMonthLabel($aMonthNbr, $year)
	{
		$sn = $this->iDateLocale->GetShortMonthName($aMonthNbr);
		$ln = $this->iDateLocale->GetLongMonthName($aMonthNbr);
		switch ($this->month->iStyle) {
			case MONTHSTYLE_SHORTNAME:
				$m = $sn;

				break;
			case MONTHSTYLE_LONGNAME:
				$m = $ln;

				break;
			case MONTHSTYLE_SHORTNAMEYEAR2:
				$m = $sn . " '" . substr('' . $year, 2);

				break;
			case MONTHSTYLE_SHORTNAMEYEAR4:
				$m = $sn . ' ' . $year;

				break;
			case MONTHSTYLE_LONGNAMEYEAR2:
				$m = $ln . " '" . substr('' . $year, 2);

				break;
			case MONTHSTYLE_LONGNAMEYEAR4:
				$m = $ln . ' ' . $year;

				break;
			case MONTHSTYLE_FIRSTLETTER:
				$m = $sn[0];

				break;
		}

		return $m;
	}

	// Stroke month scale and gridlines
	public function StrokeMonths($aYCoord, $getHeight = false)
	{
		if ($this->month->iShowLabels) {
			$img = $this->iImg;
			$img->SetFont($this->month->iFFamily, $this->month->iFStyle, $this->month->iFSize);
			$yt = $aYCoord + $img->top_margin;
			$yb = $yt + $img->GetFontHeight() + $this->month->iTitleVertMargin + $this->month->iFrameWeight;
			if ($getHeight) {
				return $yb - $img->top_margin;
			}
			$monthnbr = $this->GetMonthNbr($this->iStartDate) - 1;
			$xt	   = $img->left_margin + $this->iLabelWidth;
			$xb	   = $img->width - $img->right_margin + 1;

			$img->SetColor($this->month->iBackgroundColor);
			$img->FilledRectangle($xt, $yt, $xb, $yb);

			$img->SetLineWeight($this->month->grid->iWeight);
			$img->SetColor($this->month->iTextColor);
			$year = 0 + strftime('%Y', $this->iStartDate);
			$img->SetTextAlign('center');
			if ($this->GetMonthNbr($this->iStartDate) == $this->GetMonthNbr($this->iEndDate)
				&& $this->GetYear($this->iStartDate) == $this->GetYear($this->iEndDate)) {
				$monthwidth = $this->GetDayWidth() * ($this->GetMonthDayNbr($this->iEndDate) - $this->GetMonthDayNbr($this->iStartDate) + 1);
			} else {
				$monthwidth = $this->GetDayWidth() * ($this->GetNumDaysInMonth($monthnbr, $year) - $this->GetMonthDayNbr($this->iStartDate) + 1);
			}
			// Is it enough space to stroke the first month?
			$monthName = $this->GetMonthLabel($monthnbr, $year);
			if ($monthwidth >= 1.2 * $img->GetTextWidth($monthName)) {
				$img->SetColor($this->month->iTextColor);
				$img->StrokeText(
					round($xt + $monthwidth / 2 + 1),
					round($yb - $this->month->iTitleVertMargin),
					$monthName
				);
			}
			$x = $xt + $monthwidth;
			while ($x < $xb) {
				$img->SetColor($this->month->grid->iColor);
				$img->Line($x, $yt, $x, $yb);
				$this->month->grid->Stroke($img, $x, $yb, $x, $img->height - $img->bottom_margin);
				++$monthnbr;
				if ($monthnbr == 12) {
					$monthnbr = 0;
					++$year;
				}
				$monthName  = $this->GetMonthLabel($monthnbr, $year);
				$monthwidth = $this->GetDayWidth() * $this->GetNumDaysInMonth($monthnbr, $year);
				if ($x + $monthwidth < $xb) {
					$w = $monthwidth;
				} else {
					$w = $xb - $x;
				}

				if ($w >= 1.2 * $img->GetTextWidth($monthName)) {
					$img->SetColor($this->month->iTextColor);
					$img->StrokeText(
						round($x + $w / 2 + 1),
						round($yb - $this->month->iTitleVertMargin),
						$monthName
					);
				}
				$x += $monthwidth;
			}
			$img->SetColor($this->month->iFrameColor);
			$img->SetLineWeight($this->month->iFrameWeight);
			$img->Rectangle($xt, $yt, $xb, $yb);

			return $yb - $img->top_margin;
		}

		return $aYCoord;
	}

	// Stroke year scale and gridlines
	public function StrokeYears($aYCoord, $getHeight = false)
	{
		if ($this->year->iShowLabels) {
			$img = $this->iImg;
			$yt  = $aYCoord + $img->top_margin;
			$img->SetFont($this->year->iFFamily, $this->year->iFStyle, $this->year->iFSize);
			$yb = $yt + $img->GetFontHeight() + $this->year->iTitleVertMargin + $this->year->iFrameWeight;

			if ($getHeight) {
				return $yb - $img->top_margin;
			}

			$xb   = $img->width - $img->right_margin + 1;
			$xt   = $img->left_margin + $this->iLabelWidth;
			$year = $this->GetYear($this->iStartDate);
			$img->SetColor($this->year->iBackgroundColor);
			$img->FilledRectangle($xt, $yt, $xb, $yb);
			$img->SetLineWeight($this->year->grid->iWeight);
			$img->SetTextAlign('center');
			if ($year == $this->GetYear($this->iEndDate)) {
				$yearwidth = $this->GetDayWidth() * ($this->GetYearDayNbr($this->iEndDate) - $this->GetYearDayNbr($this->iStartDate) + 1);
			} else {
				$yearwidth = $this->GetDayWidth() * ($this->GetNumDaysInYear($year) - $this->GetYearDayNbr($this->iStartDate) + 1);
			}

			// The space for a year must be at least 20% bigger than the actual text
			// so we allow 10% margin on each side
			if ($yearwidth >= 1.20 * $img->GetTextWidth('' . $year)) {
				$img->SetColor($this->year->iTextColor);
				$img->StrokeText(
					round($xt + $yearwidth / 2 + 1),
					round($yb - $this->year->iTitleVertMargin),
					$year
				);
			}
			$x = $xt + $yearwidth;
			while ($x < $xb) {
				$img->SetColor($this->year->grid->iColor);
				$img->Line($x, $yt, $x, $yb);
				$this->year->grid->Stroke($img, $x, $yb, $x, $img->height - $img->bottom_margin);
				++$year;
				$yearwidth = $this->GetDayWidth() * $this->GetNumDaysInYear($year);
				if ($x + $yearwidth < $xb) {
					$w = $yearwidth;
				} else {
					$w = $xb - $x;
				}

				if ($w >= 1.2 * $img->GetTextWidth('' . $year)) {
					$img->SetColor($this->year->iTextColor);
					$img->StrokeText(
						round($x + $w / 2 + 1),
						round($yb - $this->year->iTitleVertMargin),
						$year
					);
				}
				$x += $yearwidth;
			}
			$img->SetColor($this->year->iFrameColor);
			$img->SetLineWeight($this->year->iFrameWeight);
			$img->Rectangle($xt, $yt, $xb, $yb);

			return $yb - $img->top_margin;
		}

		return $aYCoord;
	}

	// Stroke table title (upper left corner)
	public function StrokeTableHeaders($aYBottom)
	{
		$img = $this->iImg;
		$xt  = $img->left_margin;
		$yt  = $img->top_margin;
		$xb  = $xt + $this->iLabelWidth;
		$yb  = $aYBottom + $img->top_margin;

		if ($this->tableTitle->iShow) {
			$img->SetColor($this->iTableHeaderBackgroundColor);
			$img->FilledRectangle($xt, $yt, $xb, $yb);
			$this->tableTitle->Align('center', 'top');
			$this->tableTitle->Stroke($img, $xt + ($xb - $xt) / 2 + 1, $yt + 2);
			$img->SetColor($this->iTableHeaderFrameColor);
			$img->SetLineWeight($this->iTableHeaderFrameWeight);
			$img->Rectangle($xt, $yt, $xb, $yb);
		}

		$this->actinfo->Stroke($img, $xt, $yt, $xb, $yb, $this->tableTitle->iShow);

		// Draw the horizontal dividing line
		$this->dividerh->Stroke($img, $xt, $yb, $img->width - $img->right_margin, $yb);

		// Draw the vertical dividing line
		// We do the width "manually" since we want the line only to grow
		// to the left
		$fancy = $this->divider->iStyle == 'fancy';
		if ($fancy) {
			$this->divider->iStyle = 'solid';
		}

		$tmp					= $this->divider->iWeight;
		$this->divider->iWeight = 1;
		$y					  = $img->height - $img->bottom_margin;
		for ($i = 0; $i < $tmp; ++$i) {
			$this->divider->Stroke($img, $xb - $i, $yt, $xb - $i, $y);
		}

		// Should we draw "fancy" divider
		if ($fancy) {
			$img->SetLineWeight(1);
			$img->SetColor($this->iTableHeaderFrameColor);
			$img->Line($xb, $yt, $xb, $y);
			$img->Line($xb - $tmp + 1, $yt, $xb - $tmp + 1, $y);
			$img->SetColor('white');
			$img->Line($xb - $tmp + 2, $yt, $xb - $tmp + 2, $y);
		}
	}

	// Main entry point to stroke scale
	public function Stroke()
	{
		if (!$this->IsRangeSet()) {
			Util\JpGraphError::RaiseL(6022);
			//("Gantt scale has not been specified.");
		}
		$img = $this->iImg;

		// If minutes are displayed then hour interval must be 1
		if ($this->IsDisplayMinute() && $this->hour->GetIntervall() > 1) {
			Util\JpGraphError::RaiseL(6023);
			//('If you display both hour and minutes the hour intervall must be 1 (Otherwise it doesn\' make sense to display minutes).');
		}

		// Stroke all headers. As argument we supply the offset from the
		// top which depends on any previous headers

		// First find out the height of each header
		$offy   = $this->StrokeYears(0, true);
		$offm   = $this->StrokeMonths($offy, true);
		$offw   = $this->StrokeWeeks($offm, true);
		$offd   = $this->StrokeDays($offw, true);
		$offh   = $this->StrokeHours($offd, true);
		$offmin = $this->StrokeMinutes($offh, true);

		// ... then we can stroke them in the "backwards order to ensure that
		// the larger scale gridlines is stroked over the smaller scale gridline
		$this->StrokeMinutes($offh);
		$this->StrokeHours($offd);
		$this->StrokeDays($offw);
		$this->StrokeWeeks($offm);
		$this->StrokeMonths($offy);
		$this->StrokeYears(0);

		// Now when we now the oaverall size of the scale headers
		// we can stroke the overall table headers
		$this->StrokeTableHeaders($offmin);

		// Now we can calculate the correct scaling factor for each vertical position
		$this->iAvailableHeight = $img->height - $img->top_margin - $img->bottom_margin - $offd;

		$this->iVertHeaderSize = $offmin;
		if ($this->iVertSpacing == -1) {
			$this->iVertSpacing = $this->iAvailableHeight / $this->iVertLines;
		}
	}
}
